Board = {};
Player = {};
PlayerObserver = {};
BoardLocation = { row : "value", column : "value" };
PieceState = { type : "value", color : "value", location : BoardLocation };
Move = { source : BoardLocation, destination : BoardLocation, piece : "value",
        capturedPiece : "value", promotedPiece : "value", state : "value" };
BoardState = { moves : [Move], pieces : [PieceState] };

Board.authorizePlayer = [actorRef(PlayerObserver), "value"];
Player.move = [BoardLocation, BoardLocation, "value"];
PlayerObserver.authorizationAccepted = ["value", actorRef(Player)];
PlayerObserver.authorizationRejected = [];
PlayerObserver.moveRejected = ["value"];
PlayerObserver.boardStateChanged = [BoardState];
PlayerObserver.moved = [BoardLocation, BoardLocation, "value", "value"];

ChessClient = actor(function(prefs) {
    this.resourcePrefix = prefs.resourcePrefix;
    this.board = null;
    this.root = prefs.hostElement;
    this.gameElem = document.createElement("div");
    this.gameElem.className = "game";
    this.root.appendChild(this.gameElem);
    this.cellElems = [];
    this.pieceElems = [];
    this.selectionAllowed = false;
    this.selectedCell = null;
    this.reversed = false;
    var self = this;
    this.initStatus();
    this.initMovesTable();
    this.cells = [];
    for (var i = 0; i < 8; ++i) {
        var row = [];
        this.cells.push(row);
        for (var j = 0; j < 8; ++j) {
            row.push(null);
        }
    }
    this.boardState = null;
    this.promotionHandler = null;
    this.initPromotion();
});
ChessClient.prototype.initStatus = function() {
    this.statusElem = document.createElement("div");
    this.statusElem.className = "statusBar";
    this.gameElem.appendChild(this.statusElem);
    Util.setText(this.statusElem, "Подключение к серверу");
}
ChessClient.prototype.initPromotion = function() {
    this.promotionElem = document.createElement("div");
    this.promotionElem.className = "promotionContainer";
    this.gameElem.appendChild(this.promotionElem);
    this.promotionDialogElem = document.createElement("div");
    this.promotionDialogElem.className = "dialog";
    this.promotionElem.appendChild(this.promotionDialogElem);
    var pieces = [ { type : "QUEEN", name : "Ферзь" }, { type : "ROOK", name : "Ладья" },
                   { type : "BISHOP", name : "Слон" }, { type : "KNIGHT", name : "Конь" } ];
    var self = this;
    for (var i = 0; i < pieces.length; ++i) {
        var piece = pieces[i];
        var itemElem = document.createElement("div");
        itemElem.className = "item";
        this.promotionDialogElem.appendChild(itemElem);
        var pieceElem = document.createElement("div");
        pieceElem.className = "white " + this.pieceTypeToName(piece.type) + " piece";
        itemElem.appendChild(pieceElem);
        itemElem.appendChild(document.createTextNode(piece.name));
        itemElem.onclick = (function(type) {
            return function(e) {
                if (self.promotionHandler != null) {
                    self.promotionHandler(type);
                }
            }
        })(piece.type);
    }
}
ChessClient.prototype.showPromotion = function(handler) {
    this.promotionHandler = handler;
    this.promotionElem.style.display = "block";
}
ChessClient.prototype.hidePromotion = function() {
    this.promotionElem.style.display = "none";
}
ChessClient.prototype.initBoard = function(reversed) {
    var self = this;
    if (this.boardElem) {
        this.gameElem.removeChild(this.boardElem);
    }
    this.boardElem = document.createElement("div");
    this.boardElem.className = "board";
    this.gameElem.appendChild(this.boardElem);
    this.initCoordinates(reversed);
    for (var i = 0; i < 8; ++i) {
        var row = [];
        this.cellElems.push(row);
        var piecesRow = [];
        this.pieceElems.push(piecesRow);
        for (var j = 0; j < 8; ++j) {
            var cellElem = document.createElement("div");
            var rowClass = "row" + (!reversed ? i + 1 : 8 - i);
            var columnClass = "col" + (!reversed ? j + 1 : 8 - j);
            cellElem.className = rowClass + " " + columnClass + " " +
                    (i % 2 == j % 2 ? "black" : "white") + " cell";
            cellElem.onclick = (function(row, column) {
                return function() {
                    self.cellClick(row, column);
                }
            })(i, j);
            this.boardElem.appendChild(cellElem);
            row.push(cellElem);
            piecesRow.push(null);
        }
    }
}
ChessClient.prototype.initCoordinates = function(reversed) {
    this.colCoords = "abcdefgh";
    for (var i = 0; i < 8; ++i) {
        var rowClass = "row" + (!reversed ? i + 1 : 8 - i);
        var columnClass = "col" + (!reversed ? i + 1 : 8 - i);
        var coordElem = document.createElement("div");
        Util.setText(coordElem, i + 1);
        coordElem.className = rowClass + " left-coord";
        this.boardElem.appendChild(coordElem);

        coordElem = document.createElement("div");
        Util.setText(coordElem, i + 1);
        coordElem.className = rowClass + " right-coord";
        this.boardElem.appendChild(coordElem);

        coordElem = document.createElement("div");
        Util.setText(coordElem, this.colCoords[i]);
        coordElem.className = columnClass + " top-coord";
        this.boardElem.appendChild(coordElem);

        coordElem = document.createElement("div");
        Util.setText(coordElem, this.colCoords[i]);
        coordElem.className = columnClass + " bottom-coord";
        this.boardElem.appendChild(coordElem);
    }
}
ChessClient.prototype.initMovesTable = function() {
    var movesWrapper = document.createElement("div");
    movesWrapper.className = "movesWrapper";
    this.gameElem.appendChild(movesWrapper);
    movesTable = document.createElement("table");
    movesTable.className = "moves";
    this.movesElem = document.createElement("tbody");
    movesTable.appendChild(this.movesElem);
    movesWrapper.appendChild(movesTable);
    this.addMoveRow(1);
}
ChessClient.prototype.play = function(board, token) {
    this.board = board;
    this.player = null;
    Util.setText(this.statusElem, "Регистрация...");
    board.authorizePlayer(this, token);
}
ChessClient.prototype.authorizationRejected = function() {
    Util.setText(this.statusElem, "Не удалось зарегистрироваться в игре");
}
ChessClient.prototype.authorizationAccepted = function(color, player) {
    this.color = color;
    this.player = player;
    this.initBoard(color == "BLACK");
    Util.setText(this.statusElem, "Получение состояния доски");
}
ChessClient.prototype.boardStateChanged = function(state) {
    this.boardState = {};
    this.boardState.pieces = state.pieces;
    for (var i = 0; i < 8; ++i) {
        for (var j = 0; j < 8; ++j) {
            var pieceElem = this.pieceElems[i][j];
            if (pieceElem != null) {
                pieceElem.parentNode.removeChild(pieceElem);
            }
        }
    }
    for (var i = 0; i < state.pieces.length; ++i) {
        var piece = state.pieces[i];
        if (piece.location == null) {
            continue;
        }
        var pieceElem = document.createElement("div");
        pieceElem.className = this.getPieceClass(piece);
        var row = piece.location.row;
        var column = piece.location.column;
        this.pieceElems[row][column] = pieceElem;
        this.cellElems[row][column].appendChild(pieceElem);
        this.cells[row][column] = piece;
    }

    this.boardState.moves = [];
    for (var i = 0; i < state.moves.length; ++i) {
        this.addMove(state.moves[i]);
    }

    this.updateState();
}
ChessClient.prototype.updateState = function() {
    var moves = this.boardState.moves;
    var playerToMove = moves.length % 2 == 0 ? "WHITE" : "BLACK";
    var lastMove = moves.length > 0 ? moves[moves.length - 1] : null;
    var lastState = lastMove != null ? lastMove.state : "NONE";
    if (playerToMove == this.color) {
        var notification;
        if (lastState == "DRAW") {
            notification = "Игра окончена. Пат";
            this.selectionAllowed = false;
        } else if (lastState == "CHECKMATE") {
            notification = "Вам мат!";
            this.selectionAllowed = false;
        } else if (lastState == "CHECK") {
            notification = "Шах";
            this.selectionAllowed = true;
        } else {
            notification = "Ваш ход";
            this.selectionAllowed = true;
        }
        notifier.notify("Мини-игры - Шахматы", notification);
        Util.setText(this.statusElem, notification);
    } else {
        var notification;
        if (lastState == "DRAW") {
            notification = "Игра окончена. Пат";
        } else if (lastState == "CHECKMATE") {
            notification = "Вы поставили мат. Поздравляем!";
        } else {
            notification = "Ход противника";
        }
        Util.setText(this.statusElem, notification);
        this.selectionAllowed = false;
    }
    this.selectedCell = null;
}
ChessClient.prototype.getPieceClass = function(piece) {
    var selected = "";
    if (piece.location != null && this.selectedCell != null) {
        if (piece.location.row == this.selectedCell.row &&
                piece.location.column == this.selectedCell.column) {
            selected = "selected ";
        }
    }
    return selected + (piece.color == "WHITE" ? "white" : "black") + " piece " +
            this.pieceTypeToName(piece.type);
}
ChessClient.prototype.updatePieceAt = function(location) {
    var piece = this.cells[location.row][location.column];
    if (piece == null || piece.location == null) {
        return;
    }
    var elem = this.pieceElems[location.row][location.column];
    elem.className = this.getPieceClass(piece);
}
ChessClient.prototype.pieceTypeToName = function(pieceType) {
    switch (pieceType) {
        case "PAWN":
            return "pawn";
        case "KNIGHT":
            return "knight";
        case "BISHOP":
            return "bishop";
        case "ROOK":
            return "rook";
        case "QUEEN":
            return "queen";
        case "KING":
            return "king";
    }
}
ChessClient.prototype.cellClick = function(row, column) {
    if (!this.selectionAllowed) {
        return;
    }
    if (this.selectedCell == null) {
        var piece = this.cells[row][column];
        if (piece != null && piece.color == this.color) {
            this.selectedCell = { row : row, column : column };
            this.updatePieceAt(this.selectedCell);
        }
    } else {
        var piece = this.cells[row][column];
        if (piece != null && piece.color == this.color) {
            var oldSelected = this.selectedCell;
            this.selectedCell = { row : row, column : column };
            this.updatePieceAt(oldSelected);
            this.updatePieceAt(this.selectedCell);
        } else {
            var movedPiece = this.cells[this.selectedCell.row][this.selectedCell.column];
            this.selectionAllowed = false;
            var needsPromotion = false;
            if (movedPiece.type == "PAWN") {
                needsPromotion = this.color == "WHITE" ? row == 7 : row == 0;
            }
            if (needsPromotion) {
                var self = this;
                this.showPromotion(function(type) {
                    self.hidePromotion();
                    Util.setText(self.statusElem, "Обработка хода");
                    self.player.move(this.selectedCell, { row : row, column : column }, type);
                });
            } else {
                Util.setText(this.statusElem, "Обработка хода");
                this.player.move(this.selectedCell, { row : row, column : column }, null);
            }
        }
    }
}
ChessClient.prototype.moveRejected = function(reason) {
    this.selectionAllowed = true;
    var textualReason = "Неверный ход";
    switch (reason) {
        case "CHECK":
            textualReason = "Неверный ход. Нельзя оставлять короля под ударом";
            break;
    }
    Util.setText(this.statusElem, textualReason);
}
ChessClient.prototype.moved = function(source, destination, promotedType, state) {
    this.updatePieceAt(source);
    var piece = this.cells[source.row][source.column];
    var capturedPiece = this.cells[destination.row][destination.column];
    var move = { source : source, destination : destination,
            capturedPiece : capturedPiece != null ? capturedPiece.type : null,
            piece : piece.type, state : state };
    this.addMove(move);
    if (capturedPiece != null) {
        capturedPiece.location = null;
        var capturedElem = this.pieceElems[destination.row][destination.column];
        capturedElem.parentNode.removeChild(capturedElem);
    }
    var elem = this.pieceElems[source.row][source.column];
    elem.parentNode.removeChild(elem);
    this.cellElems[destination.row][destination.column].appendChild(elem);
    this.pieceElems[source.row][source.column] = null;
    this.cells[source.row][source.column] = null;
    piece.location = destination;
    this.pieceElems[destination.row][destination.column] = elem;
    this.cells[destination.row][destination.column] = piece;
    if (promotedType != null) {
        piece.type = promotedType;
    }
    this.updatePieceAt(destination);
    if (piece.type == "KING" && Math.abs(source.column - destination.column) == 2) {
        var rookColumn = destination.column > source.column ? 7 : 0;
        var rook = this.cells[source.row][rookColumn];
        var rookTarget = rookColumn == 0 ? 3 : 5;
        this.cells[source.row][rookColumn] = null;
        this.cells[source.row][rookTarget] = rook;
        rook.location.column = rookTarget;
        elem = this.pieceElems[source.row][rookColumn];
        elem.parentNode.removeChild(elem);
        this.pieceElems[source.row][rookColumn] = null;
        this.cellElems[source.row][rookTarget].appendChild(elem);
        this.pieceElems[source.row][rookTarget] = elem;
        this.updatePieceAt(rook.location);
    }
    this.updateState();
}
ChessClient.prototype.addMove = function(move) {
    var text = this.pieceTypeToText(move.piece);
    if (move.promotedPiece != null) {
        text = "";
    }
    text += this.locationToText(move.source);
    text += move.capturedPiece != null ? "x" : "-";
    text += this.locationToText(move.destination);
    if (move.promotedPiece != null) {
        text += this.pieceTypeToText(move.piece);
    }
    if (move.piece == "KING" && Math.abs(move.source.column - move.destination.column) == 2) {
        text = move.source.column < move.destination.column ? "0-0" : "0-0-0";
    }
    switch (move.state) {
        case "CHECK":
            text += "+";
            break;
        case "CHECKMATE":
            text += "#";
            break;
    }
    var moves = this.boardState.moves;
    moves.push(move);
    var color = moves.length % 2 == 1 ? "white" : "black"; 
    if (color == "white") {
        var elem = document.createElement("td");
        elem.className = "white";
        Util.setText(elem, text);
        this.lastMoveRow.appendChild(elem);
    } else {
        var elem = document.createElement("td");
        elem.className = "black";
        Util.setText(elem, text);
        this.lastMoveRow.appendChild(elem);
        switch (move.state) {
            case "DRAW":
            case "CHECKMATE":
                break;
            default:
                this.addMoveRow(Math.floor(moves.length / 2) + 1);
                break;
        }
    }
}
ChessClient.prototype.addMoveRow = function(index) {
    var moveRow = document.createElement("tr");
    moveRow.className = "move";
    var numberElem = document.createElement("td");
    numberElem.className = "number";
    Util.setText(numberElem, index + ".");
    moveRow.appendChild(numberElem);
    this.movesElem.appendChild(moveRow);
    this.lastMoveRow = moveRow;
}
ChessClient.prototype.pieceTypeToText = function(pieceType) {
    switch (pieceType) {
        case "PAWN":
            return "";
        case "KNIGHT":
            return "К";
        case "BISHOP":
            return "С";
        case "ROOK":
            return "Л";
        case "QUEEN":
            return "Ф";
        case "KING":
            return "Kр";
    }
}
ChessClient.prototype.locationToText = function(location) {
    return this.colCoords[location.column] + (location.row + 1);
}